package pl.com.qapps.datastore.base;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import pl.com.qapps.datastore.base.MultiQueryComponent.Order;


import com.google.appengine.api.datastore.AccessibilityHelper;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterPredicate;
import com.google.appengine.api.datastore.Query.SortPredicate;

/**
 * A class that generates multiple lists of queries to execute to satisfy the
 * {@link MultiQueryComponent}s given to it.
 * 
 * Each component contains multiple {@link List}s of {@link FilterPredicate}s
 * and an order in which MultiQuery should produce queries that contain these
 * filters.
 * 
 * If the order of a {@link MultiQueryComponent} is set to {@link Order#SERIAL}
 * the given filters are applied to sequentially generated sets of queries. This
 * usually denotes that the result of the sequentially generated queries can be
 * concatenated to produce a valid result set. However this class makes no
 * guarantees to this effect and this assurance must come from the code that
 * constructs this class.
 * 
 * If the order of a {@link MultiQueryComponent} is set to
 * {@link Order#PARALLEL} the given filters are used to generate multiple
 * queries (one for each list of filters) that are returned together. This is
 * usually used to denote that results must be merged in memory to create a
 * valid result set. However this class makes no guarantees to this effect and
 * this assurance must come from the code that constructs this class.
 * 
 * {@link MultiQueryBuilder#iterator()} will provide an iterator that generates
 * these queries in the given order. The iterator generates each list of queries
 * as they are needed. In this way, if the user request is satisfied the first
 * few sets of queries, the remaining queries need never be created. This is
 * important because the total number of queries generated by this class is
 * mult_i(|component_i.filters|).
 * 
 * This class preserves the order in which {@link MultiQueryComponent} are given
 * to it when applying the filters. However queries are generated most optimally
 * when {@link Order#PARALLEL} are the last to be applied. Thus to provide a
 * convenient way to push components with {@link Order#PARALLEL} to the back,
 * {@link MultiQueryComponent} are sortable.
 * 
 * {@link MultiQueryComponent}s with different {@link Order}s can be added to
 * the same {@link MultiQueryBuilder} in any configuration.
 * 
 */
class MultiQueryBuilder implements Iterable<List<Query>> {
	final Query baseQuery;
	final List<MultiQueryComponent> components;
	final Set<EntityFilter> entityFilters;
	final boolean hasParallelQueries;

	public MultiQueryBuilder(Query query,
			List<FilterPredicate> remainingFilters,
			Set<EntityFilter> entityFilters,
			List<MultiQueryComponent> components) {
		if (components == null) {
			throw new NullPointerException();
		}
		this.baseQuery = cloneQueryWithFilters(query, remainingFilters);
		this.components = components;
		this.entityFilters = entityFilters;

		boolean hasParallelComponents = false;
		for (MultiQueryComponent c : components) {
			if (c.getOrder() == Order.PARALLEL) {
				hasParallelComponents = true;
				break;
			}
		}
		this.hasParallelQueries = hasParallelComponents;
	}

	static Query cloneQueryWithFilters(Query query,
			List<FilterPredicate> filters) {
		return AccessibilityHelper.cloneQueryWithFilters(query, filters);
	}

	@Override
	public Iterator<List<Query>> iterator() {
		return new MultiQueryIterator(this);
	}

	public List<SortPredicate> getSortPredicates() {
		return baseQuery.getSortPredicates();
	}

	public boolean isKeysOnly() {
		return baseQuery.isKeysOnly();
	}

	public boolean hasParallelQueries() {
		return hasParallelQueries;
	}

	public Set<EntityFilter> getEntityFilters() {
		return entityFilters;
	}
}
